#include <iomanip>
#include <time.h>
#include <stdio.h>

#include "talk/base/helpers.h"
#include "talk/base/logging.h"
#include "talk/base/physicalsocketserver.h"
#include "talk/base/ssladapter.h"
#include "talk/xmpp/xmppclientsettings.h"
#include "talk/examples/login/xmppthread.h"
#include "talk/examples/login/xmppauth.h"
#include "talk/p2p/client/httpportallocator.h"
#include "talk/p2p/client/sessionmanagertask.h"
#include "presencepushtask.h"
#include "presenceouttask.h"
#include "jingleinfotask.h"
#include "talk/session/tunnel/tunnelsessionclient.h"
#include "talk/xmpp/constants.h"
#include "textchatrecv.h"

#include "PipeClient.h"
#include "PipeEp.h"
#include "XMPPCallback.h"
//K2S@&jus
const char* MONARCH_USER_AGENT = "monarch";


const char* CONSOLE_COMMANDS = "Available commands:\n"
  "\n"
  "  roster       - Prints the online friends from your roster.\n"
//  "  pipe jid local-port remote-port [udp|tcp] -  Initiate a pipe.\n"
  "  send [jid or roster number] msg_to_send  - Send chat message\n"
  "  test [jid or roster number] msg_size_in_bytes  - Send test data.\n"
  "  show  - shows state of pipes\n"
  "  quit     -    Quits the application.\n"
  "";

PipeClient::PipeClient(buzz::XmppClient *xmppclient, XMPPCallback *xc,cricket::MediaEngine* media_engine) :
  xmpp_client_(xmppclient), xmppCallback_(xc), media_engine_(media_engine)
{
  roster_ = new RosterMap;
  worker_thread_ = NULL;
  auto_accept_ = true;

  media_client_=NULL;
  call_=NULL;
  incoming_call_=false;
  portallocator_flags_ = 0;
  allow_local_ips_=true;
  initial_protocol_ = (cricket::PROTOCOL_JINGLE);
  secure_policy_ = (cricket::SEC_DISABLED);
  xmppclient->SignalStateChange.connect(this, &PipeClient::OnStateChange);
  std::cout << CONSOLE_COMMANDS;
}

PipeClient::~PipeClient()
{
  delete roster_;
}

void
PipeClient::OnStateChange(buzz::XmppEngine::State state)
{
  switch (state)
    {
  case buzz::XmppEngine::STATE_START:
    std::cout << "Connecting..." << std::endl;
    break;
  case buzz::XmppEngine::STATE_OPENING:
    std::cout << "Logging in. " << std::endl;
    break;
  case buzz::XmppEngine::STATE_OPEN:
    std::cout << "Logged in as " << xmpp_client_->jid().Str() << std::endl;
    OnSignon();
    break;
  case buzz::XmppEngine::STATE_CLOSED:
    std::cout << "Logged out." << std::endl;
    break;
    }
}




void PipeClient::OnSessionCreate(cricket::Session* session, bool initiate) {
  session->set_allow_local_ips(true);
  session->set_current_protocol(initial_protocol_);
}

bool PipeClient::ConnectTo(const std::string& remote_jid, ConnectionCallback* cc)
{

        std::cout << "Connecting psudotcp pipe\n";

        std::string pkt_size = "2000";

        std::string jid_str = remote_jid;

        RosterMap::iterator iter = roster_->find(jid_str);
        if (iter == roster_->end())
        {
           std::cout << "Error: JID not found in roster \n";
           return false;
        }
        else
        {

            buzz::Jid jid = iter->second.jid;

            talk_base::Thread *thread = new talk_base::Thread();
            PipeEp *pipe = new PipeEp(cc, this, thread, true, jid_str, pkt_size, pkt_size );
            pipes_.push_back(pipe);
            thread->Start();
            thread->Post(pipe, PIPE_EP_START_SRC);

            if (!call_ && media_engine_) {

                            call_ = media_client_->CreateCall();

                            int bandwidth = cricket::kAutoBandwidth;
                            cricket::CallOptions options;
                            options.is_video = true;
                            options.video_bandwidth = bandwidth;
                            call_->set_send_to_voicemail(false);
                            session_ = call_->InitiateSession(jid, options);

                          media_client_->SetFocus(call_);
                          //if (call_->video()) {
                            //if (!options.is_muc) {
                              //call_->SetLocalRenderer(local_renderer_);
                              //call_->SetVideoRenderer(session_, 0, remote_renderer_);
                            //}
                          //}
            }


        }
        return true;

}

void
PipeClient::ParseLine(const std::string& line)
{
  std::vector < std::string > words;
  int start = -1;
  int state = 0;
  for (int index = 0; index <= static_cast<int> (line.size()); ++index)
    {
      if (state == 0)
        {
          if (!isspace(line[index]))
            {
              start = index;
              state = 1;
            }
        }
      else
        {
          //assert(state == 1);
          //assert(start >= 0);
          if (isspace(line[index]))
            {
              std::string word(line, start, index - start);
              words.push_back(word);
              start = -1;
              state = 0;
            }
        }
    }

  // Global commands
  if ((words.size() == 1) && (words[0] == "quit"))
    {
      exit(0);
    }
  if ((words.size() == 1) && (words[0] == "show"))
    {
      ShowPipes();
      return;
    }
  if ((words.size() == 1) && (words[0] == "roster"))
    {
      PrintRoster();
    }
  else if (((words.size() == 2) || (words.size() == 3)) && (words[0] == "test"))
    {
      std::string pkt_size = "512";
      if ((words.size() == 3))
        {
          pkt_size = words[2];
        }

      std::string jid = FindInRoster(words[1]);
      if (jid.empty())
      {
         std::cout << "Error: JID not found in roster \n";
      }
      else
      {

          ConnectionCallback *cc =  xmppCallback_->onIncomingTunnelRequest();
          PipeClient::ConnectTo(jid, cc);
      }
    }
  else if (  (words.size() > 2) && (words[0] == "send") )
      {

        std::string jid = FindInRoster(words[1]);

        std::string msg = line.substr(2 + words[0].length() + words[1].length(), line.length());

        if (jid.empty())
        {
            std::cout << "Error: JID or Index not found in roster \n";
        }
        else
        {
            SendChat(jid,msg);
        }
      }

  else if (  (words.size() > 2) && (words[0] == "PBUFMSG") )
        {

          std::string cmd = words[0];
          std::string strsize = words[1];
          int msgsize = atoi (strsize.c_str());
          std::string hex_pbuf =  words[2];
          char pbmsg[msgsize+8];
          int inum;

          for(int i=0;i<msgsize;i++)
          {
              sscanf(hex_pbuf.substr(i*2,2).c_str(),"%x",&inum);
              pbmsg[i] = (char) inum;
          }
          std::cout << "GOTPBUFMSG len=" << msgsize << "msg=["<< pbmsg <<"]";

          xmppCallback_->handlePBUFMSG(pbmsg,msgsize);
        }
  else
    {
      std::cout << CONSOLE_COMMANDS;
    }
}


std::string
PipeClient::FindInRoster(std::string jid)
{

  RosterMap::iterator iter = roster_->find(jid);
  if (iter != roster_->end())
  {
     return jid;
  }

  int index = atoi (jid.c_str());
  int i = 1;
  iter = roster_->begin();
  while (iter != roster_->end())
    {
      if(i==index)
        {
          return iter->second.jid.BareJid().Str();
        }
      iter++;
      i++;
    }

  return "";
}

void
PipeClient::OnRequestSignaling()
{
  session_manager_.get()->OnSignalingReady();
}

void
PipeClient::OnJingleInfo(const std::string & relay_token, const std::vector<std::string> &relay_addresses,
    const std::vector<talk_base::SocketAddress> &stun_addresses)
{
  std::vector<std::string>::const_iterator i;
  std::cout << "relay token: " << relay_token << std::endl;
  std::cout << "relay addresses " << relay_addresses.size() << std::endl;
  for (i = relay_addresses.begin(); i != relay_addresses.end(); i++)
    {
      std::cout << *i << std::endl;
    }
  std::cout << "stun addresses " << stun_addresses.size() << std::endl;
  std::vector<talk_base::SocketAddress>::const_iterator j;
  for (j = stun_addresses.begin(); j != stun_addresses.end(); j++)
    {
      std::cout << (*j).ToString() << std::endl;
    }
    port_allocator_->SetStunHosts(stun_addresses);
    port_allocator_->SetRelayHosts(relay_addresses);
    port_allocator_->SetRelayToken(relay_token);
}



void
PipeClient::OnTextChatRecv(const  buzz::Jid  &from, const  buzz::Jid  &to,  const  std::string &msg)
{
//  std::string key = item.jid.Str()
  std::string m = * new std::string(msg);
  std::string f = from.Str();

  xmppCallback_->onChatMessageReceived(f,m);
}

const char*
PipeClient::DescribeStatus(buzz::Status::Show show, const std::string& desc)
{
  switch (show)
    {
  case buzz::Status::SHOW_XA:
    return desc.c_str();
  case buzz::Status::SHOW_ONLINE:
    return "online";
  case buzz::Status::SHOW_AWAY:
    return "away";
  case buzz::Status::SHOW_DND:
    return "do not disturb";
  case buzz::Status::SHOW_CHAT:
    return "ready to chat";
  default:
    return "offline";
    }
}

void
PipeClient::PrintRoster()
{
  std::cout << "Roster contains " << roster_->size()<< " callable\n";
  RosterMap::iterator iter = roster_->begin();
  int i = 1;
  while (iter != roster_->end())
    {
      std::cout << i << " "<< iter->second.jid.BareJid().Str() << "/" << iter->second.jid.resource() << " " << DescribeStatus(iter->second.show ,iter->second.status) << "\n";
      iter++;
      i++;
    }

}

void
PipeClient::OnStatusUpdate(const buzz::Status &status)
{
  std::cout << "update from " << status.jid().Str() << std::endl;

  RosterItem item;
  item.jid = status.jid();
  item.show = status.show();
  item.status = status.status();

  std::string key = item.jid.Str();

  if (status.available() && (key.find("/monarch",0) != std::string::npos ))
//  if (status.available() && status.voice_capability())
    {
      std::cout << "Adding to roster: "<< key.c_str() << "\n";
      (*roster_)[key] = item;
      xmppCallback_->onRosterAdd(key);
    }
  else
    {
      std::cout << "Removing from roster: "<< key.c_str() << "\n";
      RosterMap::iterator iter = roster_->find(key);
      if (iter != roster_->end())
        roster_->erase(iter);
    }
}

void
PipeClient::OnMessage(talk_base::Message *m)
{

  switch (m->message_id)
    {
  case MSG_STOP:
    {
      talk_base::Thread *thread = talk_base::ThreadManager::CurrentThread();
      //    delete session_;
      thread->Stop();
      break;
    }
  case MSG_TERMINATE_PIPE:
    {
      std::cout << "in pipe client terminating pipe\n";
      PipeEp *pipe = (PipeEp*) (m->pdata);
      delete pipe;
      for (std::vector<PipeEp*>::iterator it = pipes_.begin(); it != pipes_.end(); ++it)
        {
          if (*it == pipe)
            {
              pipes_.erase(it);
              break;
            }
        }
    }
    break;
  case MSG_CHECK_PIPE:
    {
      std::cout << "checking pipe liveness\n";
      PipeEp *pipe = (PipeEp*) (m->pdata);
      for (std::vector<PipeEp*>::iterator it = pipes_.begin(); it != pipes_.end(); ++it)
        {
          if (*it == pipe)
            {
              pipe->ConnectTimeout();
              return;
            }
        }
    }
    break;
    }
}

void
PipeClient::ShowPipes(void)
{
  for (std::vector<PipeEp*>::iterator it = pipes_.begin(); it != pipes_.end(); ++it)
    {
      (*it)->Show();
    }
}



void PipeClient::RefreshStatus() {
  int media_caps = media_client_->GetCapabilities();
  my_status_.set_jid(xmpp_client_->jid());
  my_status_.set_available(true);
  my_status_.set_show(buzz::Status::SHOW_ONLINE);
  my_status_.set_priority(0);
  my_status_.set_know_capabilities(true);
  my_status_.set_pmuc_capability(true);
  my_status_.set_voice_capability(
      (media_caps & cricket::MediaEngine::AUDIO_RECV) != 0);
  my_status_.set_video_capability(
      (media_caps & cricket::MediaEngine::VIDEO_RECV) != 0);
  my_status_.set_camera_capability(
      (media_caps & cricket::MediaEngine::VIDEO_SEND) != 0);
  my_status_.set_is_google_client(true);
  my_status_.set_version("1.0.0.67");
  presence_out_->Send(my_status_);
}

void
PipeClient::OnSignon()
{
  std::string client_unique = xmpp_client_->jid().Str();
  talk_base::InitRandom(client_unique.c_str(), client_unique.size());

  worker_thread_ = new talk_base::Thread();
  // The worker thread must be started here since initialization of
  // the ChannelManager will generate messages that need to be
  // dispatched by it.
  worker_thread_->Start();

  network_manager_ = new talk_base::BasicNetworkManager();

  port_allocator_ = new cricket::HttpPortAllocator(network_manager_, MONARCH_USER_AGENT);

  //if (portallocator_flags_ != 0) {
    port_allocator_->set_flags(0);
  //}

  session_manager_.reset(new cricket::SessionManager(port_allocator_, worker_thread_));
  session_manager_.get()->SignalRequestSignaling.connect(this, &PipeClient::OnRequestSignaling);
  session_manager_->SignalSessionCreate.connect(this, &PipeClient::OnSessionCreate);
  session_manager_.get()->OnSignalingReady();


  cricket::SessionManagerTask * session_manager_task = new cricket::SessionManagerTask(xmpp_client_, session_manager_.get());
  session_manager_task->EnableOutgoingMessages();
  session_manager_task->Start();

  buzz::JingleInfoTask *jingle_info_task = new buzz::JingleInfoTask(xmpp_client_);
  jingle_info_task->RefreshJingleInfoNow();
  jingle_info_task->SignalJingleInfo.connect(this, &PipeClient::OnJingleInfo);
  jingle_info_task->Start();

  buzz::TextChatRecvTask *text_chat_recv_task = new buzz::TextChatRecvTask(xmpp_client_);
  text_chat_recv_task->SignalTextChatReceived.connect(this, &PipeClient::OnTextChatRecv);
  text_chat_recv_task->Start();

  tunnel_client_.reset(new cricket::TunnelSessionClient(xmpp_client_->jid(), session_manager_.get()));
  tunnel_client_.get()->SignalIncomingTunnel.connect(this, &PipeClient::OnIncomingTunnel);

  if (media_engine_!=NULL) {

    media_client_ = new cricket::MediaSessionClient(
        xmpp_client_->jid(),
        session_manager_.get(),
        media_engine_,
        new cricket::DeviceManager());

    media_client_->SignalCallCreate.connect(this, &PipeClient::OnCallCreate);
    media_client_->SignalCallDestroy.connect(this, &PipeClient::OnCallDestroy);

    std::cout << "created media client\n";
    //media_client_->SignalDevicesChange.connect(this,&PipeClient::OnDevicesChange);
    media_client_->set_secure(secure_policy_);

  }

  buzz::PresencePushTask *presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
  presence_push_->SignalStatusUpdate.connect(this, &PipeClient::OnStatusUpdate);
  presence_push_->Start();

  presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
  presence_out_->Start();

  RefreshStatus();
  xmppCallback_->onXmppConnected();

}


void PipeClient::OnCallCreate(cricket::Call* call) {
  std::cout << "call created\n";
  call->SignalSessionState.connect(this, &PipeClient::OnSessionState);
  call->SignalMediaSourcesUpdate.connect(this, &PipeClient::OnMediaSourcesUpdate);
}

void PipeClient::OnMediaSourcesUpdate(cricket::Call* call,
                                      cricket::Session* session,
                                      const cricket::MediaSources& sources) {
  /*
  for (cricket::NamedSources::const_iterator it = sources.video().begin();
       it != sources.video().end(); ++it) {
    if (it->removed) {
      RemoveStaticRenderedView(it->ssrc);
    } else {
      // TODO: Make dimensions and positions more configurable.
      int offset = (50 * static_views_accumulated_count_) % 300;
      AddStaticRenderedView(session, it->ssrc, 640, 400, 30,
                            offset, offset);
    }
  }

  SendViewRequest(session);
  */
}


void PipeClient::OnCallDestroy(cricket::Call* call) {
  if (call == call_) {
    std::cout << "call destroyed\n";
    call_ = NULL;
    session_ = NULL;
  }

}

void PipeClient::Accept(const cricket::CallOptions& options) {
  std::cout << "accepting call...\n";
  ASSERT(call_ && incoming_call_);
  ASSERT(call_->sessions().size() == 1);
  call_->AcceptSession(call_->sessions()[0], options);
  media_client_->SetFocus(call_);

  if (call_->video()) {

    //call_->SetLocalRenderer(local_renderer_);
    // The client never does an accept for multiway, so this must be 1:1,
    // so there's no SSRC.
    //call_->SetVideoRenderer(session_, 0, remote_renderer_);

  }
  incoming_call_ = false;
}


void PipeClient::OnSessionState(cricket::Call* call,
                                cricket::BaseSession* session,
                                cricket::BaseSession::State state) {

  if (state == cricket::Session::STATE_RECEIVEDINITIATE) {
    buzz::Jid jid(session->remote_name());
    std::cout << "Incoming call from " <<  jid.Str().c_str();
    call_ = call;
    session_ = session;
    incoming_call_ = true;
    if (call->video()) {
        /*
      local_renderer_ =
          cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
      remote_renderer_ =
          cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
      */

    }
    cricket::CallOptions options;
    if (auto_accept_) {
      Accept(options);
    }
  } else if (state == cricket::Session::STATE_SENTINITIATE) {
    std::cout << "calling\n";
    if (call->video()) {
       /*
      local_renderer_ =
          cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
      remote_renderer_ =
          cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
          */

    }

  } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) {
     std::cout << "call answered\n";
  } else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
     std::cout << "call not answered\n";
  } else if (state == cricket::Session::STATE_INPROGRESS) {
     std::cout << "call in progress\n";
    //call->SignalSpeakerMonitor.connect(this, &CallClient::OnSpeakerChanged);
    //call->StartSpeakerMonitor(session);
  } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) {
    std::cout << "other side hung up\n";
  }

}


void
PipeClient::OnIncomingTunnel(cricket::TunnelSessionClient* client, buzz::Jid jid, std::string description, cricket::Session* session)
{

  std::cout << "Incoming Tunnel from " << jid.Str() << ": " << description << std::endl;

  if (strncmp(description.c_str(), "monarch_msg_size", 14) == 0)
    {
      ConnectionCallback* cc = xmppCallback_->onIncomingTunnelRequest();

      if(cc != NULL)
      {

        std::string lport("1024");
        std::string user(jid.Str());
        talk_base::Thread *thread = talk_base::ThreadManager::CurrentThread();

        std::string desc(description.substr(8));

        PipeEp *pipe = new PipeEp(cc, this, thread, false, user, desc, lport);
        pipes_.push_back(pipe);
        pipe->OnIncomingTunnel(client, jid, description, session);
        thread->Post(pipe, PIPE_EP_START_DST);
      }
    }
  else
    {
      std::cout << "not a pipe request, rejecting\n";
      client->DeclineTunnel(session);
    }
}


void PipeClient::SendChat(const std::string& to, const std::string msg) {
  buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE);
  stanza->AddAttr(buzz::QN_TO, to);
  stanza->AddAttr(buzz::QN_ID, talk_base::CreateRandomString(16));
  stanza->AddAttr(buzz::QN_TYPE, "chat");
  stanza->AddAttr(buzz::QN_LANG, "en"); //lang
  buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY);
  body->SetBodyText(msg);
  stanza->AddElement(body);

  xmpp_client_->SendStanza(stanza);
  delete stanza;
}

void PipeClient::SetNick(const std::string& muc_nick) {
  my_status_.set_nick(muc_nick);

  // TODO: We might want to re-send presence, but right
  // now, it appears to be ignored by the MUC.
  //
  // presence_out_->Send(my_status_); for (MucMap::const_iterator itr
  // = mucs_.begin(); itr != mucs_.end(); ++itr) {
  // presence_out_->SendDirected(itr->second->local_jid(),
  // my_status_); }

  //console_->PrintLine("Nick set to '%s'.", muc_nick.c_str());
}


void PipeClient::Reject() {
  ASSERT(call_ && incoming_call_);
  call_->RejectSession(call_->sessions()[0]);
  incoming_call_ = false;
}

void PipeClient::Quit() {
  talk_base::Thread::Current()->Quit();
}
